#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "imbus_internal.h"
#include "im_datalist.h"
#include "im_object.h"
#include "im_stream.h"

struct _IMObjectImpl
{
    /**< private >**/
    IMInt                refcount;
    IMDataList          *attachments;
};

/* ============================= Internal func ============================= */
static void     __im_object_initialize  (IMPointer      obj);
static void     __im_object_finalize    (IMPointer      obj);
static void     __im_object_copy        (IMPointer      dest,
                                         IMConstPointer src);
static IMBool   __im_object_serialize   (IMConstPointer obj,
                                         IMPointer      stream);
static IMBool   __im_object_deserialize (IMPointer      obj,
                                         IMPointer      stream);
static void     __im_object_class_init  (IMPointer      klass);

static void     __im_initially_owned_initialize(IMPointer      obj);

/* ========================== IMObject methods =========================== */
static void
__im_object_initialize (IMPointer obj)
{
    IMObject *p = (IMObject *)obj;

#if IM_ENABLE_DEBUG
    _im_verbose ("%p: ->ctor\n", obj);
#endif

    p->impl = im_slice_new0 (IMObjectImpl);
    _im_assert (p->impl);

    /* initially floating. */
    p->impl->refcount = -1;
    im_datalist_init (&p->impl->attachments);
}

static void
__im_object_finalize (IMPointer obj)
{
    IMObject *p = (IMObject *)obj;

#if IM_ENABLE_DEBUG
    _im_verbose ("%p: <-dtor\n", obj);
#endif

    im_datalist_clear (&p->impl->attachments);
    im_slice_delete(IMObjectImpl, p->impl);
}

static IMBool
__foreach_copy_attachments (IMQuark key_id, IMPointer att, IMPointer object)
{
    im_object_set_quark_attachment (object, key_id, im_object_clone (att));
    return TRUE;
}
static void
__im_object_copy (IMPointer dest, IMConstPointer src)
{
    im_datalist_clear (&((IMObject*)dest)->impl->attachments);
    im_datalist_foreach (&((IMObject*)src)->impl->attachments, __foreach_copy_attachments, dest);
}

static IMBool
__foreach_serialize_attachments (IMQuark key_id, IMPointer att, IMPointer stream)
{
    return im_stream_put_c_string ((IMStream*)stream, im_quark_to_string (key_id), -1) &&
           im_stream_put_object ((IMStream*)stream, att);
}
static IMBool
__im_object_serialize (IMConstPointer obj, IMPointer stream)
{
    IMSize len = im_datalist_length (&((const IMObject*)obj)->impl->attachments);

    if (len) {
        /* serialize all attachments */
        im_stream_put_uint32 ((IMStream*)stream, len);
        return im_datalist_foreach (&((const IMObject*)obj)->impl->attachments,
                                    __foreach_serialize_attachments,
                                    stream);
    } else {
        return im_stream_put_void ((IMStream*)stream);
    }
    return FALSE;
}
static IMBool
__im_object_deserialize (IMPointer obj, IMPointer stream)
{
    IMChar   *key = 0;
    IMUInt32  len;

    if (im_stream_get_data_type (stream) == IM_TYPE_VOID)
        return im_stream_get_void (stream);

    if (!im_stream_get_data_type (stream) == IM_TYPE_UINT32 ||
        !im_stream_get_uint32 (stream, &len))
        return FALSE;

    while (len) {
        IMPointer  att;
        IMSize     key_len;
        IMType     type;

        type = im_stream_get_data_type (stream);
        if (type != IM_TYPE_C_STRING)
            break;

        key_len = im_stream_get_data_size (stream);

        if (!key_len)
            break;

        key = im_realloc(key, key_len + 1);

        _im_assert (key);

        if (!im_stream_get_c_string (stream, key, key_len + 1))
            break;

        key [key_len] = 0;

        type = im_stream_get_data_type (stream);

        if (!IM_TYPE_IS_OBJECT(type))
            break;

        att = im_object_new (type);

        _im_assert (att);

        if (!im_stream_get_object (stream, att)) {
            im_object_unref (att);
            break;
        }

        /* The ownership of att will be assumed by
         * this method.
         */
        im_object_set_attachment (obj, key, att);

        -- len;
    }

    if (key)
        im_free (key);

    return len == 0;
}

static void
__im_object_class_init (IMPointer klass)
{
    IMObjectClass *cp = (IMObjectClass *)klass;

    cp->copy        = __im_object_copy;
    cp->serialize   = __im_object_serialize;
    cp->deserialize = __im_object_deserialize;
}

void
__im_type_register_object ()
{
    static IMTypeInfo im_object_type_info =
    {
        sizeof (IMObjectClass),     /**< class_size >**/
        __im_object_class_init,     /**< class_init >**/
        0,                          /**< class_finalize >**/

        sizeof (IMObject),          /**< instance_size >**/
        __im_object_initialize,     /**< instance_init >**/
        __im_object_finalize,       /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_INVALID,      /**< Parent class >**/
                                      IM_TYPE_OBJECT,       /**< class type >**/
                                      "Object",
                                      &im_object_type_info,
                                      FALSE);
}

/* ===================== register IMInitiallyFloatingClass ==================== */
static void
__im_initially_owned_initialize (IMPointer obj)
{
  im_object_ref(obj);
}

void
__im_type_register_initially_owned ()
{
    static IMTypeInfo im_initially_owned_type_info =
    {
        sizeof (IMInitiallyOwnedClass), /**< class_size >**/
        0,                              /**< class_init >**/
        0,                              /**< class_finalize >**/

        sizeof (IMInitiallyOwned),      /**< instance_size >**/
        __im_initially_owned_initialize,/**< instance_init >**/
        0,                              /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_OBJECT,          /**< Parent class >**/
                                      IM_TYPE_INITIALLY_OWNED, /**< class type >**/
                                      "InitiallyOwned",
                                      &im_initially_owned_type_info,
                                      FALSE);
}

/* ===================== IMObjectClass related functions ==================== */
const IMObjectClass*
im_object_class_get_parent (IMConstPointer klass)
{
    return IM_OBJECT_CLASS(im_type_class_get_parent (klass));
}

/* ======================== IMObject related functions ===================== */
IMPointer
im_object_new (IMType type)
{
    IMObject *object;

    _im_return_val_if_fail (IM_TYPE_IS_OBJECT(type), 0);

    object = im_type_instance_new (type);

    _im_assert (object);

    return object;
}

IMPointer
im_object_ref (IMPointer object)
{
    IMObject *op;

    _im_return_val_if_fail (im_object_valid (object), 0);

    op = (IMObject *) object;

    /* Floating object */
    if (op->impl->refcount == -1)
        op->impl->refcount = 0;

    ++ op->impl->refcount;

    return op;
}

void
im_object_unref (IMPointer object)
{
    IMObject *op;

    _im_return_if_fail (im_object_valid (object));

    op = (IMObject *) object;

    /* Floating object */
    if (op->impl->refcount == -1)
        op->impl->refcount = 1;

    -- op->impl->refcount;

    if (op->impl->refcount <= 0)
        im_type_instance_delete (object);
}

IMBool
im_object_is_floating (IMPointer object)
{
    _im_return_val_if_fail (im_object_valid (object), FALSE);
    return ((IMObject *)object)->impl->refcount == -1;
}

void
im_object_force_floating (IMPointer object)
{
    _im_return_if_fail (((IMObject *)object)->impl->refcount <= 1);
    ((IMObject *)object)->impl->refcount = -1;
}

IMPointer
im_object_copy  (IMPointer dest, IMConstPointer src)
{
    const IMObjectClass *cp;

    _im_return_val_if_fail (im_object_valid (dest), 0);
    _im_return_val_if_fail (im_object_valid (src), 0);
    _im_return_val_if_fail (im_type_instance_is_a (src, im_type_instance_get_type (dest)), 0);

    cp = (const IMObjectClass *) im_object_get_class (dest);

    if (cp->copy)
        cp->copy (dest, src);

    return dest;
}

IMPointer
im_object_clone (IMConstPointer object)
{
    const IMObjectClass *cp;
    IMPointer      new_obj;

    _im_return_val_if_fail (im_object_valid (object), 0);

    cp = (const IMObjectClass *) im_object_get_class (object);

    new_obj = im_object_new (im_type_class_get_type (cp));

    _im_assert (new_obj != 0);

    if (cp->copy)
        cp->copy (new_obj, object);

    return new_obj;
}

IMSize
im_object_sizeof (IMConstPointer object)
{
    return im_type_instance_sizeof (object);
}

IMBool
im_object_valid (IMConstPointer object)
{
    return object && IM_IS_OBJECT(object);
}

IMType
im_object_get_type (IMConstPointer object)
{
    return im_type_instance_get_type (object);
}

const IMObjectClass *
im_object_get_class (IMConstPointer object)
{
    return IM_OBJECT_CLASS(im_type_instance_get_class (object));
}

IMBool
im_object_is_a (IMConstPointer object, IMType is_a_type)
{
    return im_type_instance_is_a (object, is_a_type);
}

IMBool
im_object_serialize (IMConstPointer object, IMPointer stream)
{
    const IMObjectClass *cp;

    _im_return_val_if_fail (im_object_valid (object), FALSE);
    _im_return_val_if_fail (IM_IS_STREAM (stream), FALSE);

    cp = (const IMObjectClass *) im_object_get_class (object);

    if (cp->serialize)
        return cp->serialize (object, stream);

    return TRUE;
}

IMBool
im_object_deserialize (IMPointer object, IMPointer stream)
{
    const IMObjectClass *cp;

    _im_return_val_if_fail (im_object_valid (object), FALSE);
    _im_return_val_if_fail (IM_IS_STREAM (stream), FALSE);

    cp = (const IMObjectClass *) im_object_get_class (object);

    if (cp->deserialize)
        return cp->deserialize (object, stream);

    return TRUE;
}

IMBool
im_object_put_to_stream (IMConstPointer object, IMPointer stream)
{
    _im_return_val_if_fail (im_object_valid (object), FALSE);
    _im_return_val_if_fail (IM_IS_STREAM (stream), FALSE);

    return im_stream_put_object (IM_STREAM (stream), object);
}

IMBool
im_object_get_from_stream (IMPointer object, IMPointer stream)
{
    IMStream *sp;
    IMType type;

    _im_return_val_if_fail (im_object_valid (object), FALSE);
    _im_return_val_if_fail (IM_IS_STREAM (stream), FALSE);

    type = im_object_get_type (object);

    sp = IM_STREAM (stream);

    if (type == im_stream_get_data_type (sp))
        return im_stream_get_object (sp, object);

    return FALSE;
}

void
im_object_set_attachment (IMPointer object, const IMChar *key, IMPointer att)
{
    _im_return_if_fail (im_object_valid (object));
    _im_return_if_fail (im_object_valid (att));
    _im_return_if_fail (key && *key);

    im_object_ref (att);

    im_datalist_set_data_full (&((IMObject*)object)->impl->attachments,
                               key,
                               att,
                               im_object_unref);
}

IMPointer
im_object_get_attachment (IMConstPointer object, const IMChar *key)
{
    _im_return_val_if_fail (im_object_valid (object), 0);
    _im_return_val_if_fail (key && *key, 0);

    return im_datalist_get_data (&((const IMObject*)object)->impl->attachments, key);
}

void
im_object_remove_attachment (IMPointer object, const IMChar *key)
{
    _im_return_if_fail (im_object_valid (object));
    _im_return_if_fail (key && *key);

    im_datalist_remove_data (&((IMObject*)object)->impl->attachments, key);
}

void
im_object_set_quark_attachment (IMPointer object, IMQuark quark, IMPointer att)
{
    _im_return_if_fail (im_object_valid (object));
    _im_return_if_fail (im_object_valid (att));
    _im_return_if_fail (quark > 0);

    im_object_ref (att);

    im_datalist_id_set_data_full (&((IMObject*)object)->impl->attachments,
                                  quark,
                                  att,
                                  im_object_unref);
}

IMPointer
im_object_get_quark_attachment (IMConstPointer object, IMQuark quark)
{
    _im_return_val_if_fail (im_object_valid (object), 0);

    return im_datalist_id_get_data (&((const IMObject*)object)->impl->attachments, quark);
}

void
im_object_remove_quark_attachment (IMPointer object, IMQuark quark)
{
    _im_return_if_fail (im_object_valid (object));

    if (!quark)
        return;

    im_datalist_id_remove_data (&((IMObject*)object)->impl->attachments, quark);
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
